package com.up.jiasai.modules.oauth.jwt;

import com.up.vms.application.base.utils.DateUtils;

import org.apache.commons.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.UUID;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

/**
 * jwtToken 工具类
 * Fish 2018/8/26
 */
@Component
public class JwtTokenUtil {
    // jwtToken有效时间 精确到毫秒,目前值是1小时后过期
    @Value("${expire.time.long}")
    private  long EXPIRE_TIME_LONG;
    private final Logger logger = LoggerFactory.getLogger(JwtTokenUtil.class);
    @Value("${jwt.privateKey}")
    private String signKey;

    public JwtTokenUtil() {
    }

    public JwtTokenUtil(String signKey) {
        this.signKey = signKey;
    }

    public static void main(String[] args) throws Exception {
        String input = "2575f612a54e2c6efabdae484172317b";
        JwtTokenUtil jwt = new JwtTokenUtil();
        jwt.setSignKey("3c842a4841c651dd941b37e7b440f903");
        jwt.createJwtToken(input);
        String output = jwt.getUserFromToken("nbpi_eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI2NjlmN2FmMC0zZjI3LTQ0ZTQtYTRjMS0wODM1OGM0MzkxOGMiLCJpYXQiOjE1NTczMDA5NzksInN1YiI6IjVDRjAzN0YxNEQ2MzhDRjBCRDRBNDY4NTQ2MjRGMUQ5LVtST0xFX0FETUlOLCBBVVRIX1dSSVRFXSIsImV4cCI6MTU1NzMwNjk3OX0.kknWQEaCJdh4TMgSK3FTrMNrWmmLeVSfmnwQg3IaaSc");
        System.out.println(output);
    }

    // 生成jwtToken
    public String createJwtToken(String openId) {
        //指定签名的时候使用的签名算法，也就是header那部分，jjwt已经将这部分内容封装好了。
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        //生成JWT的时间
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        //创建payload的私有声明（根据特定的业务需要添加，如果要拿这个做验证，一般是需要和jwt的接收方提前沟通好验证方式的）
        //Map<String,Object> claims = new HashMap<String,Object>();
        //生成签名的时候使用的秘钥secret,这个方法本地封装了的，一般可以从本地配置文件中读取，切记这个秘钥不能外露哦。它就是你服务端的私钥，在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。
        SecretKey privateKey = createPrivateKey();
        //下面就是在为payload添加各种标准声明和私有声明了
        //这里其实就是new一个JwtBuilder，设置jwt的body
        JwtBuilder builder = Jwts.builder()
                //.setClaims(claims)          //如果有私有声明，一定要先设置这个自己创建的私有的声明，这个是给builder的claim赋值，一旦写在标准的声明赋值之后，就是覆盖了那些标准的声明的
                .setId(UUID.randomUUID().toString())                  //设置jti(JWT ID)：是JWT的唯一标识，根据业务需要，这个可以设置为一个不重复的值，主要用来作为一次性token,从而回避重放攻击。
                .setIssuedAt(now)           //iat: jwt的签发时间
                .setSubject(openId + "-" + "[ROLE_ADMIN, AUTH_WRITE]")        //sub(Subject)：代表这个JWT的主体，即它的所有人，这个是一个json格式的字符串，可以存放什么userid，roldid之类的，作为什么用户的唯一标志。
                .signWith(signatureAlgorithm, privateKey);//设置签名使用的签名算法和签名使用的秘钥
        if (EXPIRE_TIME_LONG >= 0) {
            long expMillis = nowMillis + EXPIRE_TIME_LONG;
            Date exp = new Date(expMillis);
            logger.debug("过期时间: " + DateUtils.formatTime(exp));
            builder.setExpiration(exp);     //设置过期时间
        }
        return "nbpi_" + builder.compact();
    }

    // 解密jwt
    public String getUserFromToken(String token) {
        SecretKey privateKey = createPrivateKey();  //签名秘钥，和生成的签名的秘钥一模一样
        String openId = "";
        try {
            openId = Jwts.parser().setSigningKey(privateKey).parseClaimsJws(token.replace("nbpi_", "")).getBody().getSubject();  //设置需要解析的jwt
        } catch (Exception e) {
            logger.warn("Never reach here. e:{}", e);
            return "";
        }
        return openId.split("-")[0];
    }

    // 签名私钥
    private SecretKey createPrivateKey() {
        byte[] encodedKey = Base64.decodeBase64(signKey);//本地的密码解码
        SecretKey privateKey = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");// 根据给定的字节数组使用AES加密算法构造一个密钥，使用 encodedKey中的始于且包含 0 到前 leng 个字节这是当然是所有。（后面的文章中马上回推出讲解Java加密和解密的一些算法）
        return privateKey;
    }

    public void setSignKey(String signKey) {
        this.signKey = signKey;
    }
}
